﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Text;
using System.Reflection;
using System.Linq;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections.Concurrent;
using System.Runtime.Serialization;
using System.Windows.Threading;
using System.Xml;
using System.Data;
using u = FinPlusUtility.Utility;

namespace FinPlusCommon
{
    [DataContract]
    public class Item : INotifyPropertyChanged, IFinPlusItem
    {
        [DataMember]public int Index { get; set; }
        [DataMember]public int Id { get; set; }
        [DataMember]public string Name { get; set; }
        [DataMember]public string Value { get { return _value; } set { _value = value; } }
        [DataMember]public string Type { get; set; }
        [DataMember]public DateTime TimeStamp { get { return _timeStamp; } set { if (value != _timeStamp) { _timeStamp = value; } } }
        [DataMember]public DateTime Closed { get; set; }
        [DataMember]public string User { get; set; }

        public string Display { get { return GetDisplay(); } set { SetDisplay(value); } }
        public object Object { get { return GetObject(); } }
        public event PropertyChangedEventHandler PropertyChanged;

        private enum DataType { String, DateTime, Double, Function, Boolean, Bool, Int32, Unknown }
        private enum Params { FunctionName, Id, Value, Unbooked, TimeStamp }
        private static DataTable _compute;
        private IDictionary<string, IFinPlusItem> _links;
        private IList<string> _expression;
        private IList<IFinPlusItem> _totals;
        private DateTime _timeStamp;
        private bool _isDirty = true;
        private string _value;
   
        //construct
        public Item(int index, int id, string name, string value, string type, DateTime timeStamp, string user = "Unknown", IDictionary<string, string> replace = null)
        {
            Index = index;
            Id = id;
            Name = name;
            Type = type;
            Value = u.Replace(value, replace);
            TimeStamp = timeStamp;
            User = user;
        }

        public Item(int index, int id, XmlElement xmlItem, IDictionary<string, string> replace = null)
        {
            Index = index;
            Id = id;

            DateTime datetime;
            DateTime.TryParse(u.XmlTryParse(xmlItem, Params.TimeStamp.ToString(), "0/0/0"), out datetime);
            TimeStamp = datetime;

            Value = u.Replace(u.Replace(xmlItem.Attributes["Value"].Value, @"\t|\n|\r"), replace);
            Name = xmlItem.Attributes["Name"].Value;
            Type = u.XmlTryParse(xmlItem, "Type", DataType.String.ToString());
            User = u.XmlTryParse(xmlItem, "User", DataType.Unknown.ToString());
        }

        //public
        public void Refresh()
        {
            if (!_isDirty) return;
            
            _isDirty = false;
            NotifyPropertyChanged(Params.Value.ToString());
        }

        public IFinPlusItem Clone(int id)
        {
            return new Item(Index, id, Name, Value, Type, TimeStamp, User);
        }

        public void Set(object value, string user = null, bool notify = true) 
        { 
            Set(value, DateTime.Now, user, notify); 
        }
        
        public void Set(object value, DateTime timeStamp, string user = null, bool notify = true)
        {
            if (user != null) User = user;

            _timeStamp = timeStamp;

            if (value.GetType().Name.Equals(DataType.DateTime.ToString()))
                _value = ((DateTime)value).ToOADate().ToString();
            else
                _value = value.ToString();

            if (notify)
                NotifyPropertyChanged(Params.Value.ToString());
            else
                _isDirty = true;
        }

        public string Key(string key = null)
        {
            var k = string.Format("{0}|{1}|", Id, Name);
            if (k != null && Type.Equals(DataType.Function.ToString()))
                k += ((IFinPlusFunction)Object)[key];

            return k;
        }

        public void Evaluate(IFinPlusFunction func, string expression)
        {
            _expression = expression.Split(new char[] { ']', '[' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            _links = func.Where(f => _expression.Contains(f.Name)).ToDictionary(i => i.Name, i => i);

            foreach(var item in _links.Values)
                 item.PropertyChanged += Link_Update;

            if(_compute == null)
                _compute = new DataTable();

            Evaluate();
        }

        public void Total(IList<IFinPlusItem> items)
        {
            ClearLinks();

            _totals = items;
            foreach (var total in _totals)
                total.PropertyChanged += Total_Update;

            Total();
        }

        public XmlNode ToXmlNode(XmlDocument xmlDoc)
        {
            var xmlItemNode = xmlDoc.CreateNode(XmlNodeType.Element, "Item", null);

            xmlItemNode.Attributes.Append(u.XmlAttribute(xmlDoc, "Name", Name));
            xmlItemNode.Attributes.Append(u.XmlAttribute(xmlDoc, "Value", Value));
            xmlItemNode.Attributes.Append(u.XmlAttribute(xmlDoc, "Type", Type));
            xmlItemNode.Attributes.Append(u.XmlAttribute(xmlDoc, "Timestamp", TimeStamp.ToString()));
            xmlItemNode.Attributes.Append(u.XmlAttribute(xmlDoc, "User", User));

            return xmlItemNode;
        }

        //private 
        private string GetDisplay(string defaultNumFormat = "#,##0.########;-#,##0.########;0;0")
        {
            try
            {
                switch (u.EnumTryParse<DataType>(Type, DataType.Unknown))
                {
                    case DataType.DateTime: return u.FormatDateTime(_value);
                    case DataType.Double: return double.Parse(_value).ToString(defaultNumFormat);
                    default: return _value;
                }
            }
            catch
            {
                return _value;
            }
        }
        
        private object GetObject()
        {
            try
            {
                switch (u.EnumTryParse<DataType>(Type, DataType.Unknown))
                {
                    case DataType.Boolean:
                    case DataType.Bool: return bool.Parse(_value);
                    case DataType.Int32: return int.Parse(_value);
                    case DataType.DateTime: return _value.Equals("Unbooked") ? _value : (object)DateTime.FromOADate(double.Parse(_value));
                    case DataType.Double: return double.Parse(_value);
                    case DataType.Function: return Function.FromString(_value);
                    default: return _value;
                }
            }
            catch
            {
                return _value;
            }
        }

        private void SetDisplay(string value)
        {
            if (Params.FunctionName.ToString().Equals(Name) || Params.Id.ToString().Equals(Name))
                value = _value;

            var v = string.Empty;
            try
            {
                switch (u.EnumTryParse<DataType>(Type, DataType.Unknown))
                {
                    case DataType.String: v = value.ToString(); break;
                    case DataType.Boolean:
                    case DataType.Bool: value.ToString(); break;
                    case DataType.Int32: v = value.ToString(); break;
                    case DataType.DateTime: var dt = System.Convert.ToDateTime(value); v = dt.ToOADate().ToString(); break;
                    case DataType.Double: v = double.Parse(value.ToString()).ToString(); break;
                    case DataType.Function: v = value.ToString(); break;
                    case DataType.Unknown: v = value; break;
                }
                TimeStamp = DateTime.MinValue;
            }
            catch
            {
                v = _value;
            }
            Set(v);
        }

        private void Evaluate()
        {
            try
            {
                var val = double.NaN;
                var evaluate = string.Join(" ", _expression.Select(i => _links.ContainsKey(i) ? _links[i].Value : i).ToArray());
                var res = _compute.Compute(evaluate, null);
                double.TryParse(res.ToString(), out val);
    
                Set(val);
            }
            catch
            {
                Set(double.NaN);
            }
        }

        private void Total()
        {
            try
            {
                var total = 0.0;
                foreach (var i in _totals)
                    total += (double)i.Object;
                Set(total);
            }
            catch
            {
                Set(double.NaN);
            }
        }

        private void ClearLinks()
        {
            if (_links != null)
                foreach (var item in _links.Values)
                    item.PropertyChanged -= Link_Update;
            _links = null;

            Set(double.NaN);
        }

        private void NotifyPropertyChanged(string propName) 
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propName));
        }

        //event
        private void Link_Update(object sender, PropertyChangedEventArgs a) { if(_links != null) Evaluate(); }
        private void Total_Update(object sender, PropertyChangedEventArgs a) { if(_totals != null) Total(); }
    }

    public class ItemEventArgs : EventArgs
    {
        public IFinPlusItem Item { get; private set; }
        public ChangeType ChangeType { get; private set; }

        public ItemEventArgs(IFinPlusItem item, ChangeType changeType) { Item = item; ChangeType = changeType; }
    }
}










































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus